package cn.com.zsw.gblog.modules.user.controller;

import cn.com.zsw.gblog.client.QqOauthClient;
import cn.com.zsw.gblog.client.dto.GetAccessTokenDTO;
import cn.com.zsw.gblog.client.dto.GetOpenIdRes;
import cn.com.zsw.gblog.client.dto.GetUsrInfoRes;
import cn.com.zsw.gblog.config.OauthConfigValue;
import cn.com.zsw.gblog.config.cache.RedisCache;
import cn.com.zsw.gblog.constants.SysConstants;
import cn.com.zsw.gblog.modules.user.entity.GbUserEntity;
import cn.com.zsw.gblog.modules.user.entity.ThirdPartUserEntity;
import cn.com.zsw.gblog.modules.user.service.GbUserService;
import cn.com.zsw.gblog.modules.user.service.OauthService;
import cn.com.zsw.gblog.modules.user.service.ThirdPartUserService;
import cn.com.zsw.gblog.modules.user.vo.UserInfoVO;
import cn.com.zsw.gblog.utils.JwtUtils;
import cn.com.zsw.gblog.utils.UrlUtils;
import cn.com.zsw.gblog.vo.R;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xkcoding.justauth.AuthRequestFactory;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author ：shiwangzhou
 * @Date ：Created in 2020-09-17 13:21
 * @Description：
 */
@RequestMapping("oauth2")
@Api(tags = "oauth2", value = "Oauth2Controller")
@RestController
@Slf4j
public class Oauth2Controller {
    @Autowired
    QqOauthClient oauthClient;

    @Autowired
    GbUserService userService;

    @Autowired
    ThirdPartUserService thirdPartUserService;

    @Qualifier("userDetailsServiceImpl")
    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    RedisCache redisCache;
    @Autowired
    OauthService oauthService;
    /**
     * desc: 根据QQ互联的accessToken 登录本系统,,并返回本系统的token
     *
     * @return java.lang.String
     * @author: shiwangZhou
     * @date: 2020-09-21 20:04
     * @param: accessToken QQ互联的accessToken
     */
    @Autowired
    OauthConfigValue oauthConfigValue;
    @Autowired
    AuthRequestFactory factory;


    @RequestMapping("/qq/login")
    public R qqCallback(@RequestParam String accessToken) throws Exception {
        UserInfoVO userInfoVO = callback(accessToken);
        return R.SUCCESS.data(userInfoVO);
    }

    private UserInfoVO callback(String accessToken) throws Exception {
        String openIdStr = oauthClient.getOpenId(accessToken);
        log.info("请求openID返回结果:" + openIdStr);
        String substring = openIdStr.substring(10, openIdStr.length() - 4);
        GetOpenIdRes openIdRes = JSON.parseObject(substring, GetOpenIdRes.class);
        GetUsrInfoRes userInfo = oauthClient.getUserInfo(accessToken, openIdRes.getClient_id(), openIdRes.getOpen_id());
        //根据openId查询用户,
        ThirdPartUserEntity thirdPartUser = thirdPartUserService.getOne(Wrappers.<ThirdPartUserEntity>lambdaQuery().eq(ThirdPartUserEntity::getOpenId,
                openIdRes.getOpen_id()));
        GbUserEntity gbUser;
        if (thirdPartUser == null) {
            //新建用户
            gbUser = userService.createNewUser(userInfo.getNickname(), userInfo.getAvatar());
            //新建第三方用户关系
            thirdPartUser = new ThirdPartUserEntity();
            thirdPartUser.setAvatar(userInfo.getAvatar());
            thirdPartUser.setGender("男".equalsIgnoreCase(userInfo.getGender()) ? 1 : 0);
            thirdPartUser.setId(IdUtil.createSnowflake(1, 1).nextId());
            thirdPartUser.setNickname(userInfo.getNickname());
            thirdPartUser.setUserId(gbUser.getId());
            thirdPartUser.setOpenId(openIdRes.getOpen_id());
            thirdPartUser.setUserInfo(JSON.toJSONString(userInfo));
            thirdPartUser.setCreateTime(new Date());
            thirdPartUser.setUpdateTime(new Date());
            thirdPartUser.setStatus(1);
            thirdPartUserService.save(thirdPartUser);
        } else {
            gbUser = userService.getById(thirdPartUser.getUserId());
        }
        UserDetails userDetails = userDetailsService.loadUserByUsername(userInfo.getNickname());
        //手动执行登录
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        Set<String> collect = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
        Map<String, Object> claims = new HashMap<>(5);
        claims.put(SysConstants.JWT_PERMISSIONS_KEY, JSON.toJSONString(collect));
        claims.put(SysConstants.JWT_USER_NAME, gbUser.getUsername());
        String token = JwtUtils.createToken(gbUser.getId().toString(), claims);
        redisCache.setCacheObject(SysConstants.REDIS_JWT_TOKEN_ID + token, token, 2, TimeUnit.HOURS);
        UserInfoVO userInfoVO = new UserInfoVO();
        userInfoVO.setToken(token);
        userInfoVO.setNickname(userInfo.getNickname());
        userInfoVO.setAvatar(userInfo.getAvatar());
        userInfoVO.setPermission(collect);
        return userInfoVO;
    }

    /**
     * 获取第三方登录平台信息
     *
     * @param platform 第三方登录平台
     * @return cn.com.zsw.gblog.vo.R
     * @author shiwangzhou
     * @date 2021-06-09 16:52
     **/
    @GetMapping("{platform}/info")
    public R getLoginInfo(@PathVariable String platform) {
        AuthRequest authRequest = factory.get(platform);
        String authorize = authRequest.authorize(AuthStateUtils.createState());
        log.info(authorize);
        return R.SUCCESS.data(authorize);
    }

    /**
     * desc: 前端获取到QQ互联回调的授权码时,将授权码传到后台,获取access_token,
     * 并验证token执行登录,返回当前系统的token和用户信息和权限
     * 和
     *
     * @return cn.com.zsw.gblog.vo.R
     * @author: shiwangZhou
     * @date: 2020-09-23 8:40
     * @param: code  QQ互联回调的授权码
     */
    @GetMapping("qq/token")
    public R getAccessTokenByCode(String code) throws Exception {
        String accessTokenByCode = oauthClient.getAccessTokenByCode(oauthConfigValue.getQqAppId(), oauthConfigValue.getQqAppKey(),
                code, oauthConfigValue.getQqRedirectUri());
        log.info("请求access_token返回结果:" + accessTokenByCode);
        GetAccessTokenDTO accessTokenDTO = UrlUtils.paramToDTO(accessTokenByCode, GetAccessTokenDTO.class);
        UserInfoVO userInfoVO = callback(accessTokenDTO.getAccess_token());
        Assert.notNull(userInfoVO, "未能获取到token,登录失败");
        return R.SUCCESS.data(userInfoVO);
    }

    @RequestMapping("/callback/{source}")
    @Transactional
    public R login(@PathVariable("source") String source, AuthCallback callback, HttpServletRequest request) throws Exception {
        log.info("进入callback：" + source + " callback params：" + JSONObject.toJSONString(callback));
        AuthRequest authRequest = factory.get(source);
        AuthResponse<AuthUser> response = authRequest.login(callback);
        log.info(JSONObject.toJSONString(response));
        if (response.ok()) {
            final UserInfoVO userInfoVO = oauthService.oauthCallback(response.getData());
            return R.SUCCESS.data(userInfoVO);
        }
        return R.status(500).msg("登录失败");

    }
}
